package sufutian.com.englishvoicetrans;

import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

import com.google.auth.Credentials;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.speech.v1.LongRunningRecognizeRequest;
import com.google.cloud.speech.v1.RecognitionAudio;
import com.google.cloud.speech.v1.RecognitionConfig;
import com.google.cloud.speech.v1.RecognizeRequest;
import com.google.cloud.speech.v1.RecognizeResponse;
import com.google.cloud.speech.v1.SpeechGrpc;
import com.google.cloud.speech.v1.SpeechRecognitionAlternative;
import com.google.cloud.speech.v1.SpeechRecognitionResult;
import com.google.longrunning.Operation;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.Parser;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ClientInterceptors;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.internal.DnsNameResolverProvider;
import io.grpc.okhttp.OkHttpChannelProvider;
import io.grpc.stub.StreamObserver;

import static android.content.ContentValues.TAG;

/**
 * Created by sufutian on 2017/6/21.
 */

public class EnglishVoiceClient {

    /**
     * 获取token
     */
    private volatile AccessTokenTask mAccessTokenTask;

    private Context context;
    /**
     * sp name
     */
    private final String PREFS = "EnglishVoiceClient";
    /**
     * token name
     */
    private final String PREF_ACCESS_TOKEN_VALUE = "access_token_value";
    /**
     * sp存取token过期时间
     */
    private final String PREF_ACCESS_TOKEN_EXPIRATION_TIME = "access_token_expiration_time";
    /**
     * token过期时间
     */
    private final int ACCESS_TOKEN_EXPIRATION_TOLERANCE = 30 * 60 * 1000; // thirty minutes

    private final int ACCESS_TOKEN_FETCH_MARGIN = 60 * 1000; // one minute
    /**
     * 谷歌证书范围
     */
    public final List<String> SCOPE =
            Collections.singletonList("https://www.googleapis.com/auth/cloud-platform");
    /**
     * host
     */
    private static final String HOSTNAME = "speech.googleapis.com";
    /**
     * 端口
     */
    private static final int PORT = 443;
    /**
     * 谷歌语音api
     */
    private SpeechGrpc.SpeechStub mApi;


    public EnglishVoiceClient(Context context) {
        this.context = context;
        fetchAccessToken();
    }

    private void fetchAccessToken() {
        if (mAccessTokenTask != null) {
            return;
        }
        mAccessTokenTask = new AccessTokenTask();
        mAccessTokenTask.execute();
    }


    private class AccessTokenTask extends AsyncTask<Void, Void, AccessToken> {

        @Override
        protected AccessToken doInBackground(Void... voids) {


            // ***** WARNING *****
            // In this sample, we load the credential from a JSON file stored in a raw resource
            // folder of this client app. You should never do this in your app. Instead, store
            // the file in your server and obtain an access token from there.
            // *******************
            final InputStream stream = context.getResources().openRawResource(R.raw.credential);
            try {
                final GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
                        .createScoped(SCOPE);
                final AccessToken token = credentials.refreshAccessToken();
                return token;
            } catch (IOException e) {
                Log.e(TAG, "Failed to obtain access token.", e);
            }
            return null;
        }

        @Override
        protected void onPostExecute(AccessToken accessToken) {
            mAccessTokenTask = null;
            final ManagedChannel channel = new OkHttpChannelProvider()
                    .builderForAddress(HOSTNAME, PORT)
                    .nameResolverFactory(new DnsNameResolverProvider())
                    .intercept(new GoogleCredentialsInterceptor(new GoogleCredentials(accessToken)
                            .createScoped(SCOPE)))
                    .build();
            mApi = SpeechGrpc.newStub(channel);

        }
    }


    /**
     * Authenticates the gRPC channel using the specified {@link GoogleCredentials}.
     */
    private class GoogleCredentialsInterceptor implements ClientInterceptor {

        private final Credentials mCredentials;

        private Metadata mCached;

        private Map<String, List<String>> mLastMetadata;

        GoogleCredentialsInterceptor(Credentials credentials) {
            mCredentials = credentials;
        }

        @Override
        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
                final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions,
                final Channel next) {
            return new ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT>(
                    next.newCall(method, callOptions)) {
                @Override
                protected void checkedStart(Listener<RespT> responseListener, Metadata headers)
                        throws StatusException {
                    Metadata cachedSaved;
                    URI uri = serviceUri(next, method);
                    synchronized (this) {
                        Map<String, List<String>> latestMetadata = getRequestMetadata(uri);
                        if (mLastMetadata == null || mLastMetadata != latestMetadata) {
                            mLastMetadata = latestMetadata;
                            mCached = toHeaders(mLastMetadata);
                        }
                        cachedSaved = mCached;
                    }
                    headers.merge(cachedSaved);
                    delegate().start(responseListener, headers);
                }
            };
        }


        /**
         * Generate a JWT-specific service URI. The URI is simply an identifier with enough
         * information for a service to know that the JWT was intended for it. The URI will
         * commonly be verified with a simple string equality check.
         */
        private URI serviceUri(Channel channel, MethodDescriptor<?, ?> method)
                throws StatusException {
            String authority = channel.authority();
            if (authority == null) {
                throw Status.UNAUTHENTICATED
                        .withDescription("Channel has no authority")
                        .asException();
            }
            // Always use HTTPS, by definition.
            final String scheme = "https";
            final int defaultPort = 443;
            String path = "/" + MethodDescriptor.extractFullServiceName(method.getFullMethodName());
            URI uri;
            try {
                uri = new URI(scheme, authority, path, null, null);
            } catch (URISyntaxException e) {
                throw Status.UNAUTHENTICATED
                        .withDescription("Unable to construct service URI for auth")
                        .withCause(e).asException();
            }
            // The default port must not be present. Alternative ports should be present.
            if (uri.getPort() == defaultPort) {
                uri = removePort(uri);
            }
            return uri;
        }

        private Map<String, List<String>> getRequestMetadata(URI uri) throws StatusException {
            try {
                return mCredentials.getRequestMetadata(uri);
            } catch (IOException e) {
                throw Status.UNAUTHENTICATED.withCause(e).asException();
            }
        }

        private URI removePort(URI uri) throws StatusException {
            try {
                return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1 /* port */,
                        uri.getPath(), uri.getQuery(), uri.getFragment());
            } catch (URISyntaxException e) {
                throw Status.UNAUTHENTICATED
                        .withDescription("Unable to construct service URI after removing port")
                        .withCause(e).asException();
            }
        }

        private Metadata toHeaders(Map<String, List<String>> metadata) {
            Metadata headers = new Metadata();
            if (metadata != null) {
                for (String key : metadata.keySet()) {
                    Metadata.Key<String> headerKey = Metadata.Key.of(
                            key, Metadata.ASCII_STRING_MARSHALLER);
                    for (String value : metadata.get(key)) {
                        headers.put(headerKey, value);
                    }
                }
            }
            return headers;
        }
    }

    public void recognizeInputStream(final InputStream stream, final boolean longUPload) {
        if (mApi == null) {
            return;
        }

        new Thread() {
            @Override
            public void run() {

                if (longUPload) {
                    try {
                        mApi.longRunningRecognize(LongRunningRecognizeRequest.newBuilder()
                                        .setConfig(RecognitionConfig.newBuilder()
                                                .setEncoding(RecognitionConfig.AudioEncoding.FLAC)
                                                .setLanguageCode("en-US")
                                                .setSampleRateHertz(16000)
                                                .build())
                                        .setAudio(RecognitionAudio.newBuilder()
//                                                .setContent(ByteString.readFrom(stream))
                                                .setUri("gs://gcs-test-data/vr.flac")
                                                .build())
                                        .build()
                                , new StreamObserver<Operation>() {
                                    @Override
                                    public void onNext(Operation operation) {

                                        boolean done = operation.getDone();
                                        ByteString bytes = operation.toByteString();
//                                        Operation.Builder builder = operation.toBuilder();
//                                        byte[] bytes1 = operation.toByteArray();
                                        String s = operation.toString();
                                        com.google.rpc.Status error = operation.getError();
                                        Any metadata = operation.getMetadata();
                                        String name = operation.getName();
                                        ByteString nameBytes = operation.getNameBytes();
                                        Any response = operation.getResponse();
                                        Operation.ResultCase resultCase = operation.getResultCase();
                                        Parser<Operation> parserForType = operation.getParserForType();
                                        int serializedSize = operation.getSerializedSize();
                                        if(operation.getDone()){
                                            Log.e(TAG, "Error calling the API.");
                                        }
                                    }

                                    @Override
                                    public void onError(Throwable t) {
                                        Log.e(TAG, "Error calling the API.", t);
                                    }

                                    @Override
                                    public void onCompleted() {
                                        Log.i(TAG, "API completed.");
                                    }
                                });
//                        stream.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.e(TAG, "Error loading the input", e);
                    }
                } else {
                    try {
                        mApi.recognize(
                                RecognizeRequest.newBuilder()
                                        .setConfig(RecognitionConfig.newBuilder()
                                                .setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
                                                .setLanguageCode("en-US")
                                                .setSampleRateHertz(16000)
                                                .build())
                                        .setAudio(RecognitionAudio.newBuilder()
                                                .setContent(ByteString.readFrom(stream))
                                                .build())
                                        .build(),
                                new StreamObserver<RecognizeResponse>() {
                                    @Override
                                    public void onNext(RecognizeResponse response) {
                                        String text = null;
                                        if (response.getResultsCount() > 0) {
                                            final SpeechRecognitionResult result = response.getResults(0);
                                            if (result.getAlternativesCount() > 0) {
                                                final SpeechRecognitionAlternative alternative = result.getAlternatives(0);
                                                text = alternative.getTranscript();

                                            }
                                        }
                                        if (text != null) {

                                        }
                                    }

                                    @Override
                                    public void onError(Throwable t) {
                                        Log.e(TAG, "Error calling the API.", t);
                                    }

                                    @Override
                                    public void onCompleted() {
                                        Log.i(TAG, "API completed.");
                                    }
                                });
//                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.e(TAG, "Error loading the input", e);
                    }
                }

            }
        }.start();

    }

}
